# Quene (Data Structures) - javatpoint
# https://www.javatpoint.com/data-structure-queue
"""
**Concept of Queue:**
A Queue is a fundamental data structure that follows the First-In-First-Out (FIFO) principle. It represents a collection of elements where new elements are added at one end called the "rear" and existing elements are removed from the other end called the "front." It resembles a real-life queue, such as people waiting in line for a service, where the first person who enters the line is the first to be served.

**Properties of Queue:**
1. Order: The Queue preserves the order of elements in which they are added, ensuring that the first element added is the first to be removed.
2. Rear Pointer: The Queue maintains a rear pointer that points to the last element in the Queue, where new elements are added.
3. Front Pointer: The Queue also maintains a front pointer that points to the first element in the Queue, from where elements are removed.
4. Size Tracking: The Queue keeps track of its size, representing the number of elements it currently holds.
5. Empty and Full Conditions: The Queue can be empty if no elements are present, and it can be full if it reaches its maximum capacity.
6. Enqueue: Elements are added to the rear end of the Queue.
7. Dequeue: Elements are removed from the front end of the Queue.
8. Peek: Retrieves the element at the front of the Queue without removing it.
9. IsEmpty: Checks if the Queue is empty.
10. IsFull: Checks if the Queue is full.
11. Length: Returns the total number of elements in the Queue.

**Operations of Queue:**
1. Enqueue: Inserts an element at the rear end of the Queue.
2. Dequeue: Removes and returns the element from the front end of the Queue.
3. Peek: Retrieves the element at the front end of the Queue without removing it.
4. IsEmpty: Checks if the Queue is empty.
5. IsFull: Checks if the Queue is full.
6. Length: Returns the total number of elements in the Queue.

Queues are commonly used in scenarios that require handling elements in the order they arrive, such as task scheduling, job processing, breadth-first search algorithms, and managing requests in computer systems.

"""

class Queue:
    """
    A simple implementation of a queue.

    This queue class supports enqueue, dequeue, peek, is_empty, is_full, clear, size,
    and get_capacity operations.

    Attributes:
        capacity (int): The maximum capacity of the queue.
    """

    def __init__(self, capacity: int = 10):
        """
        Initialize the queue.

        Args:
            capacity (int, optional): The maximum capacity of the queue. Defaults to 10.
        """
        self._data = []
        self._capacity = capacity

    def __len__(self) -> int:
        """Return the number of elements in the queue."""
        return len(self._data)

    def __repr__(self):
        """Return a string representation of the queue."""
        return f'Queue(capacity={self._capacity}, data={self._data})'

    def __str__(self):
        """Return a formatted string representation of the queue."""
        return str(self._data)

    def enqueue(self, item: object) -> bool:
        """
        Add an item to the end of the queue.

        Args:
            item (object): The item to be added.

        Returns:
            bool: True if the enqueue operation was successful, False otherwise.
        """
        if len(self._data) >= self._capacity:
            return False
        else:
            self._data.append(item)
            return True

    def dequeue(self) -> object:
        """
        Remove and return the item at the front of the queue.

        Returns:
            object: The item at the front of the queue.

        Raises:
            IndexError: If the queue is empty.
        """
        if len(self._data) == 0:
            raise IndexError('Queue is empty')
        return self._data.pop(0)

    def peek(self) -> object:
        """
        Return the item at the front of the queue without removing it.

        Returns:
            object: The item at the front of the queue.

        Raises:
            IndexError: If the queue is empty.
        """
        if len(self._data) == 0:
            raise IndexError('Queue is empty')
        return self._data[0]

    def is_empty(self) -> bool:
        """Check if the queue is empty."""
        return len(self._data) == 0

    def is_full(self) -> bool:
        """Check if the queue is full."""
        return len(self._data) == self._capacity

    def clear(self):
        """Clear all elements from the queue."""
        self._data = []

    def size(self) -> int:
        """Return the current size of the queue."""
        return len(self._data)

    def get_capacity(self) -> int:
        """Return the maximum capacity of the queue."""
        return self._capacity


if __name__ == "__main__":
    queue = Queue(5)  # Initialize a queue with capacity 5

    # Enqueue elements into the queue
    queue.enqueue(3)
    queue.enqueue(5)
    queue.enqueue(6)
    queue.enqueue(9)
    queue.enqueue(10)

    # Try to enqueue an element when the queue is already at capacity
    assert not queue.enqueue(100), "Expected enqueue to fail because queue is at capacity"

    print(queue)  # Expected output: [3, 5, 6, 9, 10]
    assert len(queue) == 5, "Expected queue length to be 5"
    assert queue.peek() == 3, "Expected item at the front of the queue to be 3"

    # Dequeue an element
    item = queue.dequeue()  # item = 3
    print(item)  # Expected output: 3

    # Peek at the front of the queue
    front_item = queue.peek()  # front_item = 5
    print(front_item)  # Expected output: 5

    print(queue)  # Expected output: [5, 6, 9, 10]

    # Check if the queue is empty or full
    print(queue.is_empty())  # Expected output: False
    print(queue.is_full())  # Expected output: False

    # Clear the queue
    queue.clear()
    print(queue)  # Expected output: []
    print(len(queue))  # Expected output: 0

    # Check the current size and capacity of the queue
    print(queue.size())  # Expected output: 0
    print(queue.get_capacity())  # Expected output: 5
